import wx
import os
import subprocess

import wx.combo
import wx.animate
import wx.lib.buttons as buttons

from widgets import BitmapFilePicker, InputComboBox

from constants import SCAN, LOAD, COMPARE


class InputPanel(wx.Panel):

    def __init__(self, parent):

        wx.Panel.__init__(self, parent)
        self.MainFrame = parent
        
        self.rbScan = wx.RadioButton(self, -1, "Scan from source", style=wx.RB_GROUP)

        wildcard = "Python __init__.py files (*.py)|*.py"
        self.fpInit = BitmapFilePicker(self, wildcard=wildcard)
        
        self.txtVersion = wx.TextCtrl(self, -1, "")
        self.importStyle = wx.TextCtrl(self, -1, "")
        
        self.rbLoad = wx.RadioButton(self, -1, "Load from database")
        self.dbLoad = InputComboBox(self, style=wx.CB_DROPDOWN|wx.CB_READONLY)
        self.rbCompare = wx.RadioButton(self, -1, "Compare two libraries (diff)")
        self.dbCompare_1 = InputComboBox(self, style=wx.CB_DROPDOWN|wx.CB_READONLY)
        self.dbCompare_2 = InputComboBox(self, style=wx.CB_DROPDOWN|wx.CB_READONLY)

        # Create 3 themed bitmap buttons
        runBmp = self.MainFrame.CreateBitmap("run")
        killBmp = self.MainFrame.CreateBitmap("kill")

        # This is a bit tailored over py2exe, but it's the only one I know
        self.runButton = buttons.ThemedGenBitmapTextButton(self, -1, runBmp, " Start", size=(-1, 24))
        self.killButton = buttons.ThemedGenBitmapTextButton(self, -1, killBmp, " Kill", size=(-1, 24))
        
        # The animation control
        path = os.path.normpath(os.path.join(self.MainFrame.installDir, "icons", "throbber.gif"))
            
        self.animation = wx.animate.Animation(path)
        self.throb = wx.animate.AnimationCtrl(self, -1, self.animation)
        self.throb.SetUseWindowBackgroundColour()

        self.combos = [self.dbLoad, self.dbCompare_1, self.dbCompare_2]
        self.radios = [self.rbScan, self.rbLoad, self.rbCompare]
        
        self.SetProperties()
        self.DoLayout()
        self.BindEvents()


    def SetProperties(self):

        boldFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
        boldFont.SetWeight(wx.BOLD)

        self.boldFont = boldFont

        # Set a bigger for for the compile and kill buttons
        self.runButton.SetFont(self.boldFont)
        self.killButton.Enable(False)
        
        self.ChangeRadioAppearance()        


    def DoLayout(self):

        mainSizer = wx.BoxSizer(wx.HORIZONTAL)
        rightSizer = wx.BoxSizer(wx.VERTICAL)
        centerSizer = wx.BoxSizer(wx.VERTICAL)
        leftSizer = wx.BoxSizer(wx.VERTICAL)
        left_grid_sizer = wx.FlexGridSizer(2, 3, 2, 10)
        right_grid_sizer = wx.FlexGridSizer(2, 2, 5, 5)
        
        mainSizer.Add((5, 0))
        leftSizer.Add(self.rbScan, 0, wx.RIGHT|wx.TOP|wx.BOTTOM|wx.EXPAND, 10)
        leftSizer.Add((0, 5), 0)
        
        label_1 = wx.StaticText(self, -1, "Library __init__.py file:")
        left_grid_sizer.Add(label_1, 0, wx.EXPAND)

        versionLabel = wx.StaticText(self, -1, "Version:")
        left_grid_sizer.Add(versionLabel, 0, wx.EXPAND)

        importLabel = wx.StaticText(self, -1, "Import command:")
        left_grid_sizer.Add(importLabel, 0, wx.EXPAND)
        
        left_grid_sizer.Add(self.fpInit, 0, wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
        left_grid_sizer.Add(self.txtVersion, 0, wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
        left_grid_sizer.Add(self.importStyle, 0, wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
        
        left_grid_sizer.AddGrowableCol(0)
        leftSizer.Add(left_grid_sizer, 1, wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.EXPAND, 2)
        mainSizer.Add(leftSizer, 8, wx.RIGHT|wx.EXPAND, 5)

        mainSizer.Add(wx.StaticLine(self, -1, style=wx.LI_VERTICAL), 0, wx.EXPAND|wx.ALL, 5)
        mainSizer.Add((5, 0))
        
        centerSizer.Add(self.rbLoad, 0, wx.RIGHT|wx.TOP|wx.BOTTOM|wx.EXPAND, 10)
        centerSizer.Add((0, 5), 0)
        label_3 = wx.StaticText(self, -1, "Library name && version:")
        centerSizer.Add(label_3, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM, 2)
        centerSizer.Add((0, 1))
        centerSizer.Add(self.dbLoad, 0, wx.LEFT|wx.RIGHT|wx.EXPAND, 2)
        mainSizer.Add(centerSizer, 3, wx.EXPAND, 0)

        mainSizer.Add((5, 0))
        mainSizer.Add(wx.StaticLine(self, -1, style=wx.LI_VERTICAL), 0, wx.EXPAND|wx.ALL, 5)
        mainSizer.Add((5, 0))

        rightSizer.Add(self.rbCompare, 0, wx.RIGHT|wx.TOP|wx.BOTTOM|wx.EXPAND, 10)
        rightSizer.Add((0, 5), 0)

        label_4 = wx.StaticText(self, -1, "Library 1 (base):")
        right_grid_sizer.Add(label_4, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
        right_grid_sizer.Add(self.dbCompare_1, 0, wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
        label_5 = wx.StaticText(self, -1, "Library 2:")
        right_grid_sizer.Add(label_5, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
        right_grid_sizer.Add(self.dbCompare_2, 0, wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)
        right_grid_sizer.AddGrowableCol(1)
        rightSizer.Add(right_grid_sizer, 1, wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.EXPAND, 2)
        rightSizer.Add((0, 5), 0)

        mainSizer.Add(rightSizer, 4, wx.EXPAND)
        mainSizer.Add((5, 0))

        staticbox = wx.StaticBox(self, -1, "Actions")
        staticbox.SetForegroundColour(wx.RED)
        buttonSizer = wx.StaticBoxSizer(staticbox, wx.VERTICAL)

        # We have the main list filling all the space with a small reserved
        # zone on the right for the buttons
        buttonSizer.Add(self.runButton, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.LEFT|wx.RIGHT, 5)
        buttonSizer.Add((0, 2))
        buttonSizer.Add(self.throb, 0, wx.ALIGN_CENTER)
        buttonSizer.Add((0, 2))
        buttonSizer.Add(self.killButton, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.LEFT|wx.RIGHT, 5)

        buttonSizer.Show(self.throb, False)
        buttonSizer.Layout()
        mainSizer.Add(buttonSizer, 0, wx.EXPAND)
        mainSizer.Show(buttonSizer, False)

        self.buttonSizer = buttonSizer
        self.mainSizer = mainSizer
        
        for child in self.GetChildren():
            if isinstance(child, wx.StaticText):
                child.SetFont(self.boldFont)
        
        self.SetSizer(mainSizer)
        self.Fit()
        

    def BindEvents(self):

        self.fpInit.Bind(wx.EVT_FILEPICKER_CHANGED, self.OnHandlePackage)

        for child in self.GetChildren():
            if isinstance(child, InputComboBox):
                child.Bind(wx.EVT_COMBOBOX, self.MoveRadioSelection)
            elif isinstance(child, wx.RadioButton):
                child.Bind(wx.EVT_RADIOBUTTON, self.ChangeRadioAppearance)

        self.Bind(wx.EVT_BUTTON, self.OnStart, self.runButton)
        self.Bind(wx.EVT_BUTTON, self.OnKill, self.killButton)


    def PopulateCombos(self, items):

        self.items = items
        self.Freeze()

        for combo in self.combos:
            combo.Clear()

        for name, filename, scan_date, icon in items:
            bmp = self.MainFrame.CreateBitmap(icon)
            for combo in self.combos:
                combo.Append(name, clientData=bmp)
                    
        self.Thaw()
        

    # ============== #
    # Event handlers #
    # ============== #
    
    def OnStart(self, event):
        """ Handles the wx.EVT_BUTTON event for the start button. """
        
        # Delegate the action to the main frame
        self.MainFrame.OnStart()


    def OnKill(self, event):
        """ Handles the wx.EVT_BUTTON event for the kill button. """

        # Delegate the action to the main frame
        self.MainFrame.OnKill()

        # Hide the throb
        self.ShowThrobber(False)
                          

    # ================= #
    # Auxiliary methods #
    # ================= #

    def ShowThrobber(self, show):
        """
        Shows/hides the throbber.

        
        **Parameters:**

        * show: whether to show or hide the throbber.
        """
        
        # Show/hide the throb

        if not show:
            self.throb.SetAnimation(wx.animate.NullAnimation)
        else:
            self.throb.SetAnimation(self.animation)

        self.throb.SetMinSize((18, 18))
        self.buttonSizer.Layout()
        # Refresh ourselves
        self.Refresh()
        if show:
            self.throb.Play()
        else:
            self.throb.Stop()


    def OnHandlePackage(self, event):
        
        initName = event.GetPath()
        if not os.path.isfile(initName):
            return

        initName = initName.strip()
        if not initName.endswith("__init__.py"):
            return

        self.txtVersion.ChangeValue("")
        self.importStyle.ChangeValue("")

        self.MoveRadioSelection()

        wx.BeginBusyCursor()

        importName = os.path.split(os.path.split(initName)[0])[1]

        hunter = os.path.join(self.MainFrame.installDir, "modulehunter.py")
        cmd = '"%s" -u "%s" "%s" "%s"'%(self.MainFrame.pythonVersion, hunter, initName, importName)
        
        process = subprocess.Popen(cmd, shell=(not subprocess.mswindows), stdout=subprocess.PIPE)
        process.wait()
        version = process.stdout.read()

        wx.EndBusyCursor()
        yellow = wx.Colour(255, 255, 175)

        if "Error:" in version:
            message = ":".join(version.split(":")[1:])
            self.MainFrame.SendMessage("Warning", message.strip())
            self.MainFrame.SendMessage("Warning", "Unable to import library '%s'. Please check for conflicting Python versions"%importName)
            self.txtVersion.SetBackgroundColour(yellow)
            self.importStyle.SetBackgroundColour(yellow)
            self.Refresh()
            return

        self.MainFrame.AddFileToHistory(initName)
        self.importStyle.ChangeValue(importName)

        if "Warning:" in version:
            self.MainFrame.SendMessage("Warning", "Library '%s' has no __version__ or __VERSION__ attribute. Please specify it in the 'Import command' textbox"%importName)
            self.txtVersion.SetBackgroundColour(yellow)
            self.Refresh()
            return

        self.txtVersion.ChangeValue(version.strip())
        self.txtVersion.SetBackgroundColour(wx.WHITE)
        self.importStyle.SetBackgroundColour(wx.WHITE)
        self.Refresh()
        

    def Validate(self):

        errStr = ""
        
        if self.rbScan.GetValue():
            initName, importName, version = self.GetPackageInfo()
            if not initName or not os.path.isfile(initName):
                errStr = 'Invalid "__init__.py" file selected'
            elif not importName or not version:
                errStr = 'Invalid "Import command" or "Version" strings'
                
        elif self.rbLoad.GetValue():
            name = self.GetLoadingInfo()
            if not name.strip():
                errStr = 'No library selected under "Library name & version"'

        elif self.rbCompare.GetValue():
            name1, name2 = self.GetComparisonInfo()
            if not name1.strip() or not name2.strip():
                errStr = 'Please select the correct libraries under "Library 1 (base)" and "Library 2"'

        if errStr:
            self.MainFrame.RunError("Error", errStr)
            return False

        self.txtVersion.SetBackgroundColour(wx.WHITE)
        self.importStyle.SetBackgroundColour(wx.WHITE)

        return True
    

    def GetProcess(self):

        if self.rbScan.GetValue():
            return SCAN
        elif self.rbLoad.GetValue():
            return LOAD

        return COMPARE


    def GetPackageInfo(self):

        initName = self.fpInit.GetPath()
        importName = self.importStyle.GetValue()
        version = self.txtVersion.GetValue()

        return initName.strip(), importName.strip(), version.strip()


    def GetLoadingInfo(self):

        return self.dbLoad.GetValue()


    def GetComparisonInfo(self):

        return self.dbCompare_1.GetValue(), self.dbCompare_2.GetValue()

    
    def MoveRadioSelection(self, eventOrObject=None):

        newObj = self.radios[0]

        if eventOrObject is not None:

            if isinstance(eventOrObject, InputComboBox):
                obj = eventOrObject
            else:
                obj = eventOrObject.GetEventObject()

            if obj == self.dbLoad:
                self.rbLoad.SetValue(True)
                newObj = self.radios[1]
                pos = 5
            else:
                self.rbCompare.SetValue(True)
                newObj = self.radios[2]
                pos = 9
        else:
            self.rbScan.SetValue(True)
            pos = 2

        self.mainSizer.Detach(self.buttonSizer)
        self.mainSizer.Insert(pos, self.buttonSizer, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        self.mainSizer.Show(self.buttonSizer)
        self.ShowThrobber(False)
        self.mainSizer.Layout()

        self.ChangeRadioAppearance(newObj)            


    def ChangeRadioAppearance(self, eventOrObject=None):

        obj = self.rbScan
        if eventOrObject is not None:
            if isinstance(eventOrObject, wx.RadioButton):
                obj = eventOrObject
            else:
                obj = eventOrObject.GetEventObject()

        normalFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
        normalFont.SetPointSize(10)
        boldFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
        boldFont.SetPointSize(10)
        boldFont.SetWeight(wx.BOLD)

        for radio in self.radios:
            radio.SetFont((radio == obj and [boldFont] or [normalFont])[0])
            radio.Refresh()
            

    def SetInput(self, item1, item2=None, isFile=True):

        if item2 is None:
            if isFile:
                self.fpInit.SetValue(item1)
                return

            self.dbLoad.SetStringSelection(item1)
            self.MoveRadioSelection(self.dbLoad)
        else:
            self.dbCompare_1.SetStringSelection(item1)
            self.dbCompare_2.SetStringSelection(item2)
            self.MoveRadioSelection(self.dbCompare_1)


    def EnableButtons(self, enable):
        """
        Enables/disables the run buttons depending on the external
        process status.

        
        **Parameters:**

        * enable: whether to enable or disable the buttons.
        """

        # dry run and compile buttons are enabled when the kill button is
        # not, and vice-versa
        self.runButton.Enable(enable)
        self.killButton.Enable(not enable)

        self.ShowThrobber(not enable)

        self.mainSizer.Show(self.buttonSizer, not enable)
        self.mainSizer.Layout()
        self.Refresh()
        self.Update()
        
        